1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.collect;
18
19 import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly;
20 import static java.util.concurrent.TimeUnit.HOURS;
21
22 import com.google.common.annotations.GwtCompatible;
23 import com.google.common.annotations.GwtIncompatible;
24 import com.google.common.base.Function;
25 import com.google.common.collect.MapMaker.RemovalNotification;
26 import com.google.common.collect.MapMakerInternalMapTest.QueuingRemovalListener;
27 import com.google.common.testing.NullPointerTester;
28
29 import junit.framework.TestCase;
30
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.atomic.AtomicInteger;
39
40
41
42
43 @GwtCompatible(emulated = true)
44 public class MapMakerTest extends TestCase {
45
46 @GwtIncompatible("NullPointerTester")
47 public void testNullParameters() throws Exception {
48 NullPointerTester tester = new NullPointerTester();
49 tester.testAllPublicInstanceMethods(new MapMaker());
50 }
51
52 @GwtIncompatible("threads")
53
54 public void testRemovalNotification_clear() throws InterruptedException {
55
56
57
58 final CountDownLatch computingLatch = new CountDownLatch(1);
59 Function<String, String> computingFunction = new DelayingIdentityLoader<String>(computingLatch);
60 QueuingRemovalListener<String, String> listener = new QueuingRemovalListener<String, String>();
61
62 @SuppressWarnings("deprecation")
63 final ConcurrentMap<String, String> map = new MapMaker()
64 .concurrencyLevel(1)
65 .removalListener(listener)
66 .makeComputingMap(computingFunction);
67
68
69 map.put("a", "a");
70
71 final CountDownLatch computationStarted = new CountDownLatch(1);
72 final CountDownLatch computationComplete = new CountDownLatch(1);
73 new Thread(new Runnable() {
74 @Override public void run() {
75 computationStarted.countDown();
76 map.get("b");
77 computationComplete.countDown();
78 }
79 }).start();
80
81
82 computationStarted.await();
83 map.clear();
84
85 computingLatch.countDown();
86
87 computationComplete.await();
88
89
90
91
92 assertEquals(1, listener.size());
93 RemovalNotification<String, String> notification = listener.remove();
94 assertEquals("a", notification.getKey());
95 assertEquals("a", notification.getValue());
96 assertEquals(1, map.size());
97 assertEquals("b", map.get("b"));
98 }
99
100
101
102
103
104
105
106
107
108
109 @GwtIncompatible("threads")
110
111 public void testRemovalNotification_clear_basher() throws InterruptedException {
112
113
114
115
116 CountDownLatch computationLatch = new CountDownLatch(1);
117 QueuingRemovalListener<String, String> listener = new QueuingRemovalListener<String, String>();
118
119 @SuppressWarnings("deprecation")
120 final Map<String, String> map = new MapMaker()
121 .removalListener(listener)
122 .concurrencyLevel(20)
123 .makeComputingMap(new DelayingIdentityLoader<String>(computationLatch));
124
125 int nThreads = 100;
126 int nTasks = 1000;
127 int nSeededEntries = 100;
128 Set<String> expectedKeys = Sets.newHashSetWithExpectedSize(nTasks + nSeededEntries);
129
130
131 for (int i = 0; i < nSeededEntries; i++) {
132 String s = "b" + i;
133 map.put(s, s);
134 expectedKeys.add(s);
135 }
136
137 final AtomicInteger computedCount = new AtomicInteger();
138 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
139 final CountDownLatch tasksFinished = new CountDownLatch(nTasks);
140 for (int i = 0; i < nTasks; i++) {
141 final String s = "a" + i;
142 threadPool.submit(new Runnable() {
143 @Override public void run() {
144 map.get(s);
145 computedCount.incrementAndGet();
146 tasksFinished.countDown();
147 }
148 });
149 expectedKeys.add(s);
150 }
151
152 computationLatch.countDown();
153
154 while (computedCount.get() < nThreads) {
155 Thread.yield();
156 }
157 map.clear();
158 tasksFinished.await();
159
160
161
162
163 Map<String, String> removalNotifications = Maps.newHashMap();
164 for (RemovalNotification<String, String> notification : listener) {
165 removalNotifications.put(notification.getKey(), notification.getValue());
166 assertEquals("Unexpected key/value pair passed to removalListener",
167 notification.getKey(), notification.getValue());
168 }
169
170
171
172 for (int i = 0; i < nSeededEntries; i++) {
173 assertEquals("b" + i, removalNotifications.get("b" + i));
174 }
175
176
177
178 assertEquals(expectedKeys, Sets.union(map.keySet(), removalNotifications.keySet()));
179 assertTrue(Sets.intersection(map.keySet(), removalNotifications.keySet()).isEmpty());
180 }
181
182 @GwtIncompatible("threads")
183 static final class DelayingIdentityLoader<T> implements Function<T, T> {
184 private final CountDownLatch delayLatch;
185
186 DelayingIdentityLoader(CountDownLatch delayLatch) {
187 this.delayLatch = delayLatch;
188 }
189
190 @Override public T apply(T key) {
191 awaitUninterruptibly(delayLatch);
192 return key;
193 }
194 }
195
196
197
198
199
200
201
202 public static class MakerTest extends TestCase {
203 public void testInitialCapacity_negative() {
204 MapMaker maker = new MapMaker();
205 try {
206 maker.initialCapacity(-1);
207 fail();
208 } catch (IllegalArgumentException expected) {
209 }
210 }
211
212
213 public void xtestInitialCapacity_setTwice() {
214 MapMaker maker = new MapMaker().initialCapacity(16);
215 try {
216
217 maker.initialCapacity(16);
218 fail();
219 } catch (IllegalArgumentException expected) {
220 }
221 }
222
223 @SuppressWarnings("deprecation")
224 public void testExpiration_setTwice() {
225 MapMaker maker = new MapMaker().expireAfterWrite(1, HOURS);
226 try {
227
228 maker.expireAfterWrite(1, HOURS);
229 fail();
230 } catch (IllegalStateException expected) {
231 }
232 }
233
234 public void testMaximumSize_setTwice() {
235 MapMaker maker = new MapMaker().maximumSize(16);
236 try {
237
238 maker.maximumSize(16);
239 fail();
240 } catch (IllegalStateException expected) {
241 }
242 }
243
244 public void testReturnsPlainConcurrentHashMapWhenPossible() {
245 Map<?, ?> map = new MapMaker()
246 .initialCapacity(5)
247 .makeMap();
248 assertTrue(map instanceof ConcurrentHashMap);
249 }
250 }
251
252
253 public static class MaximumSizeTest extends TestCase {
254 public void testPut_sizeIsZero() {
255 ConcurrentMap<Object, Object> map =
256 new MapMaker().maximumSize(0).makeMap();
257 assertEquals(0, map.size());
258 map.put(new Object(), new Object());
259 assertEquals(0, map.size());
260 }
261
262 public void testSizeBasedEviction() {
263 int numKeys = 10;
264 int mapSize = 5;
265 ConcurrentMap<Object, Object> map =
266 new MapMaker().maximumSize(mapSize).makeMap();
267 for (int i = 0; i < numKeys; i++) {
268 map.put(i, i);
269 }
270 assertEquals(mapSize, map.size());
271 for (int i = numKeys - mapSize; i < mapSize; i++) {
272 assertTrue(map.containsKey(i));
273 }
274 }
275 }
276
277
278 public static class RecursiveComputationTest extends TestCase {
279 Function<Integer, String> recursiveComputer
280 = new Function<Integer, String>() {
281 @Override
282 public String apply(Integer key) {
283 if (key > 0) {
284 return key + ", " + recursiveMap.get(key - 1);
285 } else {
286 return "0";
287 }
288 }
289 };
290
291 ConcurrentMap<Integer, String> recursiveMap = new MapMaker()
292 .makeComputingMap(recursiveComputer);
293
294 public void testRecursiveComputation() {
295 assertEquals("3, 2, 1, 0", recursiveMap.get(3));
296 }
297 }
298
299
300
301
302 public static class ComputingTest extends TestCase {
303 public void testComputerThatReturnsNull() {
304 ConcurrentMap<Integer, String> map = new MapMaker()
305 .makeComputingMap(new Function<Integer, String>() {
306 @Override
307 public String apply(Integer key) {
308 return null;
309 }
310 });
311 try {
312 map.get(1);
313 fail();
314 } catch (NullPointerException e) { }
315 }
316
317 public void testRuntimeException() {
318 final RuntimeException e = new RuntimeException();
319
320 ConcurrentMap<Object, Object> map = new MapMaker().makeComputingMap(
321 new Function<Object, Object>() {
322 @Override
323 public Object apply(Object from) {
324 throw e;
325 }
326 });
327
328 try {
329 map.get(new Object());
330 fail();
331 } catch (ComputationException ce) {
332 assertSame(e, ce.getCause());
333 }
334 }
335 }
336 }